﻿using System;
using System.Collections.Generic;
using System.Text;
using NUnit.Framework;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using ActiveSharp.PropertyMapping;

namespace ActiveSharp.Tests
{

    /// <summary>
    /// Compare perf of this library with various alternatives.
    /// </summary>
    /// <remarks>Would be nice to include lambda expression options here, but I write the ActiveSharp codebase (including the 
    /// tests, at present) to be compatible with .NET 2.0. Here are some lambda expression timings from another source: http://zamboch.blogspot.com/2011/03/raising-property-changed-fast-and-safe.html 
    /// 
    /// Finally, at time of checkin, my timings were:
    /// 
    /// SimpleString :            11ms per 500000 calls
    /// StringWithStaticArgs :     4ms per 500000 calls
    /// StackCrawl :           15407ms per 500000 calls
    /// ActiveSharpDirect :       77ms per 500000 calls
    /// 
    /// How slow is ActiveSharp?  7 times slower than the usual (SimpleString) solution IN THIS TEST.
    /// By my calculations, if you set 300,000,000 properties (approx the workload of a batch process I recently wrote)
    /// ActiveSharp would add an extra 39 seconds to the execution time.  Considering the batch process in question 
    /// did extensive database reads and writes, and so took at total of about 24 machine-hours, 39 seconds doesn't sound
    /// like much of a problem ;-)
    ///
    /// </remarks>
    [TestFixture, Explicit]
    public class PerformanceTests
    {

        [Test]
        public void DumPerfTestResults()
        {
            DoTests(false);  // run once through to ensure caches, jitting etc is done
            DoTests(true);   // do it again, and dump the results this time
        }

        private delegate BaseEntity EntityCreationDelegate();

        private void DoTests(bool dumpResults)
        {
            IList<string> result = new List<string>();

            result.Add(DoTest(delegate(){return new SimpleString();}));
            result.Add(DoTest(delegate(){return new StringWithStaticArgs();}));
            result.Add(DoTest(delegate(){return new StackCrawl();}));
            result.Add(DoTest(delegate(){return new ActiveSharpDirect(); }));
            result.Add(DoTest(delegate() { return new ActiveSharpFast(); }));

            if (dumpResults)
            {
                foreach (string s in result)
                    Console.WriteLine(s);
            }
        }

        private string DoTest(EntityCreationDelegate del)
        {
            bool fooChanged = false;

            BaseEntity entity = del();
            string className = entity.GetType().Name;
            entity.PropertyChanged += delegate(object sender, PropertyChangedEventArgs args)
            {
                if(args.PropertyName == "Foo")
                    fooChanged = true;
            };

            entity.Foo = 1;
            Assert.IsTrue(fooChanged, "expect foo to be changed for " + className);

            const int numCalls = 500 * 1000;
            Stopwatch sw = new Stopwatch();
            sw.Start();

            for(int i = 0; i < numCalls; i++)
            {
                entity.Foo = i;
            }

            sw.Stop();
            long elapsedMilliseconds = sw.ElapsedMilliseconds;;
            return string.Format("{0} : {1}ms per {2} calls", className, elapsedMilliseconds, numCalls);
        }


        #region target classes for tests

        abstract class BaseEntity : INotifyPropertyChanged
        {
            public event PropertyChangedEventHandler PropertyChanged;

            protected void Notify(PropertyChangedEventArgs args)
            {
                if (PropertyChanged != null)
                    PropertyChanged(this, args);
            }

            public abstract int Foo { get; set; }
        }


        class SimpleString: BaseEntity
        {
            private int _foo;

            public override int Foo
            {
                get
                {
                    return _foo;
                }
                set
                {
                    _foo = value;
                    Notify(new PropertyChangedEventArgs("Foo"));
                }
            }
        }

        class StringWithStaticArgs: BaseEntity
        {
            private int _foo;

            private readonly PropertyChangedEventArgs _fooArgs = new PropertyChangedEventArgs("Foo");

            public override int Foo
            {
                get
                {
                    return _foo;
                }
                set
                {
                    _foo = value;
                    Notify(_fooArgs);
                }
            }
        }

        class StackCrawl : BaseEntity
        {
            private int _foo;

            [MethodImpl(MethodImplOptions.NoInlining)]
            private string GetPropName()
            {
                // this method is fatally flawed, and only included for performance comaprison
                // See http://www.lhotka.net/Article.aspx?area=4&id=0708c745-f009-4d09-9f91-6a349b5b0317 and the comments
                // here: http://dotnet.agilekiwi.com/blog/2007/07/claytons-interception.html
                var sf = new StackFrame(1);
                var method = sf.GetMethod();
                var propertyName = method.Name.Remove(0, 4);
                return propertyName;
            }

            public override int Foo
            {
                get
                {
                    return _foo;
                }
                set
                {
                    _foo = value;
                    Notify(new PropertyChangedEventArgs(GetPropName()));
                }
            }
        }

        // called direct because it doesn't use ActiveSharp.PropertyChangeHelper
        class ActiveSharpDirect : BaseEntity
        {
            private int _foo;

            protected void SetValue<T>(ref T field, T value)
            {
                field = value;  
                PropertyInfo changedProperty =  PropertyMap.GetProperty(this, ref field);
                Notify(new PropertyChangedEventArgs(changedProperty.Name));
            }

            public override int Foo
            {
                get
                {
                    return _foo;
                }
                set
                {
                    SetValue(ref _foo, value);
                }
            }
        }

        // uses the new "fast" activeSharp API
        class ActiveSharpFast : BaseEntity
        {
            private int _foo;

            protected void CoreSetValue<T>(ref T field, T value, string propertyName)
            {
                field = value;
                Notify(new PropertyChangedEventArgs(propertyName));
            }

            // note: SetValue and CoreSet value are split in this way, since that is the usage pattern that 
            // is necessary with class heirarchies, when using FastPropertyMap.
            // Each class in the heirarchy needs its OWN (private) SetValue method, with its own type as the generic type parameter.
            // (That's how FastPropertyMap knows which type to work with - the ordinary ("slow") property map does a hash table lookup instead)
            // The set value methods then call CoreSetValue, which is defined only in the base
            private void SetValue<T>(ref T field, T value)
            {
                CoreSetValue(ref field, value, FastPropertyMap<ActiveSharpFast>.GetPropertyName(this, ref field));
            }

            public override int Foo
            {
                get
                {
                    return _foo;
                }
                set
                {
                    SetValue(ref _foo, value);
                }
            }
        }


        #endregion
    }
}
